/*
 * @Author: 生俊甫 1758142861@qq.com
 * @Date: 2024-06-11 15:46:19
 * @LastEditors: 生俊甫 1758142861@qq.com
 * @LastEditTime: 2024-06-12 14:48:28
 * @FilePath: /sjf/2024-project/2024_centos/source/session.hpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#pragma once

#include <iostream>
#include <mutex>
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include "./log/logger.hpp"

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
typedef enum { UNLOGIN,LOGIN} statu;
/**
 * 单独session模块
 * 主要实现的是一个用户的连接
 */
class session
{
public:
    session(uint64_t sid) :_sid(sid)
    {
        DBG_LOG("session:%p 被创建",this);
    }
    ~session()
    {
        DBG_LOG("session:%p 被释放",this);
    }
    uint64_t get_sid() { return _sid; }
    uint64_t get_user() { return _uid; }
    void set_statu(statu st) { _statu = st; }
    void set_user(uint64_t uid) { _uid = uid; }
    statu get_statu() { return _statu; }
    bool user_login() { return _statu == LOGIN; }
    void set_timer(const wsserver_t::timer_ptr& tp) {_tp = tp; }
    wsserver_t::timer_ptr& get_timer() { return _tp; }

private:
    uint64_t _sid;
    uint64_t _uid;
    statu _statu;
    wsserver_t::timer_ptr _tp;
};

/**
 * 对所有的session进行管理
 * 主要实现，session的创建，销毁，通过sid获取session
 * 通过sid判断session是否存在，刷新定时时间(过期时间)
 */
#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1
using session_ptr = std::shared_ptr<session>;
class session_manager
{
public:
    session_manager(wsserver_t* server)
    :_next_sid(1),_server(server)
    {
        DBG_LOG("session_manager被创建");
    }
    ~session_manager()
    {
        DBG_LOG("session_manager被销毁");
    }
    //新增session
    session_ptr create_session(uint64_t uid,statu st)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        session_ptr sp(new session(_next_sid));
        sp->set_statu(st);
        sp->set_user(uid);
        _session.insert(std::make_pair(_next_sid,sp));
        _next_sid++;
        return sp;
    }
    //通过sid获取session信息
    session_ptr get_session_by_sid(uint64_t sid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _session.find(sid);
        if(it == _session.end())
            return session_ptr();
        return it->second;
    }
    //通过sid判断session是否存在
    bool session_in_by_sid(uint64_t sid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _session.find(sid);
        if(it == _session.end())
            return false;
        return true;
    }
    /**
     * 移除session
     */
    void remove_session_by_sid(uint64_t sid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _session.find(sid);
        if(it == _session.end())
            return;
        _session.erase(sid);
    }
    /* 添加已有的session信息，是为了在下面添加定时器时防止cancel和添加session顺序不确定而造成误删*/
    void append_session(const session_ptr& session)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.insert(std::make_pair(session->get_sid(),session));
    }
    //设置会话定时信息
    /**
     * 依赖于websocket的定时器来完成session是生命周期的管理
     * 在http通信的时候(注册，登录)session要具备生命周期，指定时间无法通信后删除
     * 也就是进入游戏大厅，或者游戏房间的时候，我们的session应该永久存在
     * 在我们退出了游戏或房间，这个session会被设置为临时的，长时间无通信后删除
     * 通常我们创建的session在不设置定时任务的情况下，都是永久存在的
     * 所有在本函数中主要有4种情况
     * 1.在session永久存在情况下，设置永久存在 (可能是正在对战期间)
     * 2.在session永久存在情况下，设置指定时间后被删除的定时任务 (可能是游戏大厅-->退出游戏)
     * 3.在session设置了定时删除情况下，将session设置为永久存在 (可能是登录-->游戏大厅页面)
     * 4.在session设置了定时删除情况下，将session重置删除时间 (可能是注册--> 登录)
     */
    void set_session_timer(uint64_t sid, int ms)
    {
        session_ptr sp = get_session_by_sid(sid);
        if(sp.get() == nullptr)
            return;
        wsserver_t::timer_ptr tp = sp->get_timer();
        //1.session永久存在情况下，设置永久存在 (可能是正在对战期间)
        if(tp.get() == nullptr && ms == SESSION_FOREVER)
            return;
        //2.session永久存在情况下，设置指定时间后被删除的定时任务
        //也就是添加删除该session操作
        else if(tp.get() == nullptr && ms != SESSION_FOREVER)
        {
            wsserver_t::timer_ptr new_tp = _server->set_timer(ms,std::bind(&session_manager::remove_session_by_sid,this,sid));
            sp->set_timer(new_tp);
        }
        //3.在session设置了定时删除情况下，将session设置为永久存在
        //也就是在有定时任务前提下，删除该定时任务并设置为永久存在
        else if (tp.get() != nullptr && ms == SESSION_FOREVER)
        {
            //因为直接cancel会导致定时任务直接被执行，然后从我们的_session中erase
            //所以我们在cancel之后要手动再给这个session添加到_session中-->_server->set_timer
            //最后再从新设置时间sp->set_timer
            //至于为什么把sp和_server互换位置是因为cancel函数的删除操作并不是被直接执行的
            //而是会经过一段时间后被执行，所以调换位置并单独设置_server的set函数操作
            tp->cancel();
            sp->set_timer(wsserver_t::timer_ptr());
            _server->set_timer(0,std::bind(&session_manager::append_session,this,sp));
        }
        //4.在session设置了定时删除情况下，将session重置删除时间
        //也就是在有定时任务的前提下，重置定时时间
        else if(tp.get() != nullptr && ms != SESSION_FOREVER)
        {
            //取消本来的定时任务
            tp->cancel();
            sp->set_timer(wsserver_t::timer_ptr());
            _server->set_timer(0,std::bind(&session_manager::append_session,this,sp));
            //重新添加新的定时时间
            wsserver_t::timer_ptr new_tp = _server->set_timer(ms,std::bind(&session_manager::remove_session_by_sid,this,sp->get_sid()));
            sp->set_timer(new_tp);
        }
    }
private:
    uint64_t _next_sid;
    std::mutex _mutex;
    std::unordered_map<uint64_t,session_ptr> _session;
    wsserver_t* _server;
};